/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// Vorbis Audio RTP Sources
// Implementation

#include "include/VorbisAudioRTPSource.hh"
#include "include/Base64.hh"

////////// VorbisBufferedPacket and VorbisBufferedPacketFactory //////////

class VorbisBufferedPacket : public BufferedPacket {
public:
    VorbisBufferedPacket();

    virtual ~VorbisBufferedPacket();

private: // redefined virtual functions
    virtual unsigned nextEnclosedFrameSize(unsigned char *&framePtr,
                                           unsigned dataSize);
};

class VorbisBufferedPacketFactory : public BufferedPacketFactory {
private: // redefined virtual functions
    virtual BufferedPacket *createNewPacket(MultiFramedRTPSource *ourSource);
};


///////// VorbisAudioRTPSource implementation ////////

VorbisAudioRTPSource *
VorbisAudioRTPSource::createNew(UsageEnvironment &env, Groupsock *RTPgs,
                                unsigned char rtpPayloadFormat,
                                unsigned rtpTimestampFrequency) {
    return new VorbisAudioRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency);
}

VorbisAudioRTPSource
::VorbisAudioRTPSource(UsageEnvironment &env, Groupsock *RTPgs,
                       unsigned char rtpPayloadFormat,
                       unsigned rtpTimestampFrequency)
        : MultiFramedRTPSource(env, RTPgs, rtpPayloadFormat, rtpTimestampFrequency,
                               new VorbisBufferedPacketFactory),
          fCurPacketIdent(0) {
}

VorbisAudioRTPSource::~VorbisAudioRTPSource() {
}

Boolean VorbisAudioRTPSource
::processSpecialHeader(BufferedPacket *packet,
                       unsigned &resultSpecialHeaderSize) {
    unsigned char *headerStart = packet->data();
    unsigned packetSize = packet->dataSize();

    resultSpecialHeaderSize = 4;
    if (packetSize < resultSpecialHeaderSize) return False; // packet was too small

    // The first 3 bytes of the header are the "Ident" field:
    fCurPacketIdent = (headerStart[0] << 16) | (headerStart[1] << 8) | headerStart[2];

    // The 4th byte is F|VDT|numPkts.
    // Reject any packet with VDT == 3:
    if ((headerStart[3] & 0x30) == 0x30) return False;

    u_int8_t F = headerStart[3] >> 6;
    fCurrentPacketBeginsFrame = F <= 1; // "Not Fragmented" or "Start Fragment"
    fCurrentPacketCompletesFrame = F == 0 || F == 3; // "Not Fragmented" or "End Fragment"

    return True;
}

char const *VorbisAudioRTPSource::MIMEtype() const {
    return "audio/VORBIS";
}


////////// VorbisBufferedPacket and VorbisBufferedPacketFactory implementation //////////

VorbisBufferedPacket::VorbisBufferedPacket() {
}

VorbisBufferedPacket::~VorbisBufferedPacket() {
}

unsigned VorbisBufferedPacket
::nextEnclosedFrameSize(unsigned char *&framePtr, unsigned dataSize) {
    if (dataSize < 2) {
        // There's not enough space for a 2-byte header.  TARFU!  Just return the data that's left:
        return dataSize;
    }

    unsigned frameSize = (framePtr[0] << 8) | framePtr[1];
    framePtr += 2;
    if (frameSize > dataSize - 2)
        return dataSize - 2; // inconsistent frame size => just return all the data that's left

    return frameSize;
}

BufferedPacket *VorbisBufferedPacketFactory
::createNewPacket(MultiFramedRTPSource * /*ourSource*/) {
    return new VorbisBufferedPacket();
}


////////// parseVorbisOrTheoraConfigStr() implementation //////////

#define ADVANCE(n) do { p += (n); rem -= (n); } while (0)
#define GET_ENCODED_VAL(n) do { u_int8_t byte; n = 0; do { if (rem == 0) break; byte = *p; n = (n*128) + (byte&0x7F); ADVANCE(1); } while (byte&0x80); } while (0); if (rem == 0) break

void parseVorbisOrTheoraConfigStr(char const *configStr,
                                  u_int8_t *&identificationHdr, unsigned &identificationHdrSize,
                                  u_int8_t *&commentHdr, unsigned &commentHdrSize,
                                  u_int8_t *&setupHdr, unsigned &setupHdrSize,
                                  u_int32_t &identField) {
    identificationHdr = commentHdr = setupHdr = NULL; // default values, if an error occur
    identificationHdrSize = commentHdrSize = setupHdrSize = 0; // ditto
    identField = 0; // ditto

    // Begin by Base64-decoding the configuration string:
    unsigned configDataSize;
    u_int8_t *configData = base64Decode(configStr, configDataSize);
    u_int8_t *p = configData;
    unsigned rem = configDataSize;

    do {
        if (rem < 4) break;
        u_int32_t numPackedHeaders = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
        ADVANCE(4);
        if (numPackedHeaders == 0) break;

        // Use the first 'packed header' only.
        if (rem < 3) break;
        identField = (p[0] << 16) | (p[1] << 8) | p[2];
        ADVANCE(3);

        if (rem < 2) break;
        u_int16_t length = (p[0] << 8) | p[1];
        ADVANCE(2);

        unsigned numHeaders;
        GET_ENCODED_VAL(numHeaders);

        Boolean success = False;
        for (unsigned i = 0; i < numHeaders + 1 && i < 3; ++i) {
            success = False;
            unsigned headerSize;
            if (i < numHeaders) {
                // The header size is encoded:
                GET_ENCODED_VAL(headerSize);
                if (headerSize > length) break;
                length -= headerSize;
            } else {
                // The last header is implicit:
                headerSize = length;
            }

            // Allocate space for the header bytes; we'll fill it in later
            if (i == 0) {
                identificationHdrSize = headerSize;
                identificationHdr = new u_int8_t[identificationHdrSize];
            } else if (i == 1) {
                commentHdrSize = headerSize;
                commentHdr = new u_int8_t[commentHdrSize];
            } else { // i == 2
                setupHdrSize = headerSize;
                setupHdr = new u_int8_t[setupHdrSize];
            }

            success = True;
        }
        if (!success) break;

        // Copy the remaining config bytes into the appropriate 'header' buffers:
        if (identificationHdr != NULL) {
            memmove(identificationHdr, p, identificationHdrSize);
            ADVANCE(identificationHdrSize);
            if (commentHdr != NULL) {
                memmove(commentHdr, p, commentHdrSize);
                ADVANCE(commentHdrSize);
                if (setupHdr != NULL) {
                    memmove(setupHdr, p, setupHdrSize);
                    ADVANCE(setupHdrSize);
                }
            }
        }
    } while (0);

    delete[] configData;
}
